#!/usr/bin/env perl -w
######################################################################
#
# cvs-clean-for-import
#
# Remove files you don't want to import into CVS. These are typically
# automatically generated files.
#
# $Id$
#
######################################################################

require 5.001;

use Getopt::Std;

######################################################################
#
# Defaults
#

%Configuration = (
		  remove_autoheader      => 1,
		  remove_autoconf        => 1,
		  remove_aclocal_m4      => 1,
		  remove_automake        => 1,
		  clean_rcs_keywords     => 1,
		  recurse                => 1,
		  verbose                => 0,
		);

$Dont_Actually_Do_Anything = 0;

######################################################################
#
# Parse commandline options
#

use Getopt::Std;

my %opt;

getopts('cChHlnrRv', \%opt);

$opt{c} && $opt{C} && error("Can't specifiy both -c and -C");
$opt{c} && ($Configuration{remove_autoconf} = 1);
$opt{C} && ($Configuration{remove_autoconf} = 0);

$opt{h} && $opt{H} && error("Can't specifiy both -h and -H");
$opt{h} && ($Configuration{remove_autoheader} = 1);
$opt{H} && ($Configuration{remove_autoheader} = 0);

$opt{l} && ($Configuration{recurse} = 0);

$opt{n} && ($Dont_Actually_Do_Anything = 1);

$opt{r} && $opt{R} && error("Can't specifiy both -r and -R");
$opt{r} && ($Configuration{clean_rcs_keywords} = 0);
$opt{R} && ($Configuration{clean_rcs_keywords} = 1);

$opt{v} && ($Configuration{verbose} = 1);

my $directory = shift || ".";

######################################################################
#
# Files and the string that indicates they are automatically generated
#

%Strings = ();

if ($Configuration{remove_autoconf}) {
    $Strings{"configure"} = "Generated automatically using autoconf";
    $Strings{"Makefile"} = "Generated automatically from Makefile.in by configure";
}

if ($Configuration{remove_autoheader}) {
    $Strings{"config.h.in"} = "Generated automatically from configure.in by autoheader";
}

if ($Configuration{remove_aclocal_m4}) {
    $Strings{"aclocal.m4"} = "aclocal.m4 generated automatically by aclocal";
}

if ($Configuration{remove_automake}) {
    $Strings{"Makefile.in"} = "Makefile.in generated automatically by automake";
}

######################################################################

do_dir($directory);

exit 0;

######################################################################
#
# do_dir
#
# Recersive function that cleans a directory
#
# Arguments: <path>
# Returns: Nothing

sub do_dir {
  my $path = shift;
  
  # Make sure path is terminated with a '/'
  $path .= "/"
    if ($path !~ /\/$/);
  
  message("Cleaning directory $path\n");
  
  my @files = directory($path);
  
  foreach $file (@files) {
    my $fullname = $path . $file;
    
    next
      if ( ! -f $fullname);
    
    if (auto_generated($path, $file)) {
      message(" deleting $fullname\n");
      unlink $fullname
	if (!$dont_actually_do_anything);
      next;
    }
    
    if (!$dont_actually_do_anything) {
      clean_file($fullname);
    }
  }
  
  if ($Configuration{recurse}) {
    
    foreach $file (@files) {
      my $fullname = $path . $file;
      
      next
	if (! -d $fullname);
      
	  do_dir($fullname);
      }
  }

}

######################################################################
#
# directory
#
# Return all the files in a given directory
#
# "." and ".." are ignored.
#
# Arguments: <path>
# Returns: Array of files
#

sub directory {
  my $dir = shift;
  
  opendir(DH, $dir) ||
    error("could not open directory $dir: $!");
  
  my $file;
  my @files = ();
  
  while (defined($file = readdir(DH))) {
    
    # ignore "." and ".."
    if (($file eq ".") || ($file eq "..")) {
      next;
    }
    
    push(@files, $file);
  }
  
  closedir(DH);
  
  return @files;
}


######################################################################
#
# auto_generated
#
# Check a file for a string indicating it was automatically generated.
#
# Arguments: Path, Filename
# Returns: 1 if autogenerated, 0 otherwise
#

sub auto_generated {
  my $path = shift;
  my $filename = shift;
  
  my $autogenerated = 0;
  
  my $string = $Strings{$filename};
  
  return 0
    if (!defined($string));
  
  my $fullname = $path . $filename;
  
  if (!open(FILE, $fullname)) {
    warning("could not open $fullname: $!\n");
    return 0;
  }
  
  while(<FILE>) {
    
    if (/$string/) {
      $autogenerated = 1;
      last;
    }
  }
  
  close(FILE);
  
  return $autogenerated;
}



######################################################################
#
# clean_file
#
# clean a file.
#
# arguments: filename
# returns: 0 on error, 1 otherwise
#

sub clean_file {
  my $filename = shift;
  
  my $tmp_file = tmp_filename();
  
  if (!open(INPUT, "<$filename")) {
    warning("could not open $filename: $!\n");
    return 0;
  }
  
  if (!open(OUTPUT, ">$tmp_file")) {
    warning("Could not open $tmp_file for writing: $!\n");
    close(INPUT);
    return 0;
  }
  
  my $changed = 0;
  
  while(<INPUT>) {
    if ($Configuration{clean_rcs_keywords}) {
      if (s/\$((Author|Date|Header|Id|Locker|Log|Name|RCSfile|Revision|Source|State)(|:[^\$]+))\$/$1/gi) {
	$changed = 1;
      }
    }
    
    print OUTPUT;
  }
  
  close(INPUT);
  close(OUTPUT);
  
  if ($changed) {
    message(" cleaned $filename");
    
    system("mv $tmp_file $filename");
    
  } else {
    unlink $tmp_file;
  }
}


######################################################################
#
# tmp_filename()
#
# Return the name of a temporary file to use.
#
# Arguments: None
# Returns: Nothing
#

sub tmp_filename() {
  $0 =~ /\/?([^\/]*)$/;
  
  my $program_name = $1;
  
  my $filename = "/tmp/" . $program_name . "." . $$;
  
  if ( -e $filename) {
    my $count = 0;
    
    my $tmp_name;
    
    do {
      
      $tmp_name = $filename . "." . $count;
      
      $count++;
      
    } while ( -e $tmp_name);
    
    $filename = $tmp_name;
    }

    return $filename;
}

######################################################################
#
# error
#
# Print error message and die.
#
# Arguments: Arguments to printf
# Returns: Doesn't

sub error {
  warning(@_);

  cleanup();

  exit(1);
}

######################################################################
#
# warning
#
# Print warning message
#
# Argumrnts: Arguments to printf
# Returns: 1

sub warning {
  my $format = shift;

  chomp($format);
  $format .= "\n";

  my $message = sprintf($format, @_);

  print STDERR $message;

  return(1);
}

######################################################################
#
# message
#
# Print a message to the user
#
# Argumrnts: Arguments to printf
# Returns: 1

sub message {
  my $format = shift;

  chomp($format);
  $format .= "\n";

  my $message = sprintf($format, @_);

  print $message;

  return(1);
}

######################################################################
#
# verbose
#
# Print a message if we in verbose mode.
#
# Arguents: Arguments to printf
# Returns: 1

sub verbose {
  message(@_) if ($Configuration{verbose});

  return(1);
}
__END__

######################################################################
#
# POD documentation
#

=head1 NAME

cvs-clean-for-import

=head1 SYNOPSIS

cvs-clean-for-import <options> [<directory>]

Clean a package for import into cvs.

=head1 DESCRIPTION

cvs-clean-for-import will remove any autogenerated files from
a directory tree and remove any offensive RCS keywords so that
it is ready to import into CVS.

=head1 COMMANDLINE ARGUMENTS

cvs-clean-for-import accepts the following commandline options:

=over 4

=item -c Remove autoconf autogenerated files. This is the default.

=item -C Don't remove autoconf autogenerated files.

=item -h Remove autoheader autogenerated files. This is the default.

=item -H Don't remove autoheader autogenerated files.

=item -l Don't recurse into subdirectories.

=item -n Don't actually do anything, just show what would be done.

=item -r Sanitize RCS keywords. This is the default.

=item -R Don't sanitize RCS keywords.

=back

=head1 SEE ALSO

cvs(1)

=head1 AUTHOR

Von Welch <vwelch@ncsa.uiuc.edu>

=cut
